#include "UnfoldPicture.h"
#include "VertexData.h"
#include <vector>
#include "utility.h"


using std::vector;

int PicWidth = 596;
int PicHeight = 300;

inline float rad2deg(float rad)
{
	return float(rad / M_PI * 180.f);
}

UnfoldPicture* UnfoldPicture::create(RollPath* pathLeft, RollPath* pathRight, float startPoint)
{
	UnfoldPicture* unfold = new UnfoldPicture;
	unfold->pathLeft = pathLeft;
	unfold->pathRight = pathRight;
	unfold->startPos = startPoint;
	unfold->initialGL();

	return unfold;
}

UnfoldPicture::UnfoldPicture()
{
}

UnfoldPicture::~UnfoldPicture()
{

	glDeleteBuffers(1, &vboRight);
	glDeleteVertexArrays(1, &vaoRight);
	glDeleteBuffers(1, &vboRightUnfold);
	glDeleteVertexArrays(1, &vaoRightUnfold);
}

void UnfoldPicture::initialGL()
{
	if (this->pathRight) 
	{
		this->initialRollGL(vaoRight, vboRight, this->pathRight, this->startPos, 1.0);

		glGenVertexArrays(1, &vaoRightUnfold);
		glBindVertexArray(vaoRightUnfold);
		glGenBuffers(1, &vboRightUnfold);
		this->UpdateUnFoldGL(vboRightUnfold, this->pathRight, 0, this->startPos, this->startPos);
	}

	if (this->pathLeft) 
	{
		this->initialRollGL(vaoLeft, vboLeft, this->pathLeft, this->startPos, 0.0f);

		glGenVertexArrays(1, &vaoLeftUnfold);
		glBindVertexArray(vaoLeftUnfold);
		glGenBuffers(1, &vboLeftUnfold);
		this->UpdateUnFoldGL(vboLeftUnfold, this->pathLeft, 0, this->startPos, this->startPos);

	}
}

void UnfoldPicture::initialRollGL(GLuint& vao, GLuint& vbo, RollPath* path, float startU, float endU)
{
	int vertexCount = path->getVertexCount();
	vector<VertexData> verties(vertexCount * 2);

	float glHeight = this->getPathLength() * float(PicHeight) / float(PicWidth);
	float uvStep = (endU - startU) / (float)vertexCount;
	auto radius = path->getRadius();
	auto pathPos = path->getPath();
	for (size_t i = 0; i < vertexCount; i++)
	{
		float x = pathPos[i * 2];
		float y = pathPos[i * 2 + 1];

		Vector3f normal(x, y - radius, 0);
		normal.normalize();

		verties[i * 2].pos[0] = x;
		verties[i * 2].pos[1] = y;
		verties[i * 2].pos[2] = -glHeight;
		verties[i * 2].normal[0] = normal.x;
		verties[i * 2].normal[1] = normal.y;
		verties[i * 2].normal[2] = normal.z;
		verties[i * 2].uv[0] = startU + float(i) * uvStep;
		verties[i * 2].uv[1] = 0;


		verties[i * 2 + 1].pos[0] = x;
		verties[i * 2 + 1].pos[1] = y;
		verties[i * 2 + 1].pos[2] = 0;
		verties[i * 2 + 1].normal[0] = normal.x;
		verties[i * 2 + 1].normal[1] = normal.y;
		verties[i * 2 + 1].normal[2] = normal.z;
		verties[i * 2 + 1].uv[0] = startU + float(i) * uvStep;
		verties[i * 2 + 1].uv[1] = 1;
	}


	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);

	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, VertexSize * vertexCount * 2, &verties[0], GL_STATIC_DRAW);

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, VertexSize, OFFSET_POINTER(0));
	glEnableVertexAttribArray(0);

	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, VertexSize, OFFSET_POINTER(sizeof(float) * 3));
	glEnableVertexAttribArray(1);

	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, VertexSize, OFFSET_POINTER(sizeof(float) * 6));
	glEnableVertexAttribArray(2);

	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

}

void UnfoldPicture::UpdateUnFoldGL(GLuint vbo, RollPath* path, int endIndex, float startU, float endU)
{
	float length = endIndex * path->getStep();
	float glHeight = this->getPathLength() * float(PicHeight) / float(PicWidth);
	auto pathPos = path->getPath();

	VertexData verties[4]{ 0 };

	verties[0].pos[0] = 0;
	verties[0].pos[1] = 0;
	verties[0].pos[2] = -glHeight;
	verties[0].normal[0] = 0;
	verties[0].normal[1] = 1;
	verties[0].normal[2] = 0;
	verties[0].uv[0] = startU;
	verties[0].uv[1] = 0;

	verties[1].pos[0] = 0;
	verties[1].pos[1] = 0;
	verties[1].pos[2] = 0;
	verties[1].normal[0] = 0;
	verties[1].normal[1] = 1;
	verties[1].normal[2] = 0;
	verties[1].uv[0] = startU;
	verties[1].uv[1] = 1;


	verties[2].pos[0] = length;
	verties[2].pos[1] = 0;
	verties[2].pos[2] = -glHeight;
	verties[2].normal[0] = 0;
	verties[2].normal[1] = 1;
	verties[2].normal[2] = 0;
	verties[2].uv[0] = endU;
	verties[2].uv[1] = 0;


	verties[3].pos[0] = length;
	verties[3].pos[1] = 0;
	verties[3].pos[2] = 0;
	verties[3].normal[0] =0;
	verties[3].normal[1] = 1;
	verties[3].normal[2] = 0;
	verties[3].uv[0] = endU;
	verties[3].uv[1] = 1;

	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, VertexSize * 4, verties, GL_STATIC_DRAW);

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, VertexSize, OFFSET_POINTER(0));
	glEnableVertexAttribArray(0);

	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, VertexSize, OFFSET_POINTER(sizeof(float) * 3));
	glEnableVertexAttribArray(1);

	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, VertexSize, OFFSET_POINTER(sizeof(float) * 6));
	glEnableVertexAttribArray(2);
}


void UnfoldPicture::draw(GLuint program, float percentage, const Matrix4f& VP, const Vector3f& eyePosition)
{
	if (percentage < 0) {
		percentage = 0;
	}
	else if (percentage > 1)
	{
		percentage = 1;
	}

	glUseProgram(program);
	glUniformMatrix4fv(1, 1, GL_FALSE, VP);
	Vector4f color(1, 1, 1, 1);
	glUniform4fv(100, 1, color);
	glUniform3fv(200, 1, eyePosition);

	if (this->pathLeft != nullptr && this->pathRight != nullptr) 
	{

		float rightOffset = this->pathRight->getRadius() / this->pathRight->getPathLength();
		this->drawPath(true, rightOffset + (1 - rightOffset) * percentage);

		float leftOffset = this->pathLeft->getRadius() / this->pathLeft->getPathLength();
		this->drawPath(false, leftOffset + (1-leftOffset) * percentage);
	} 
	else
	{
		if (this->pathRight != nullptr)
		{
			this->drawPath(true, percentage);

		} 

		if (this->pathLeft != nullptr)
		{
			this->drawPath(false, percentage);
		}
	}

}

void UnfoldPicture::drawPath(bool isRightPath, float percentage)
{
	GLuint vao, vaoUnfold, vboUnfold;
	RollPath* path = this->pathRight;
	if (isRightPath) {
		path = this->pathRight;
		vao = vaoRight;
		vaoUnfold = vaoRightUnfold;
		vboUnfold = vboRightUnfold;
	}
	else
	{
		path = this->pathLeft;
		vao = vaoLeft;
		vaoUnfold = vaoLeftUnfold;
		vboUnfold = vboLeftUnfold;
	}

	int vertexCount = path->getVertexCount();
	int startDrawIndex = int(vertexCount * percentage);
	float hlength = startDrawIndex * path->getStep();
	float radius = path->getRadius();
	float angle = hlength / radius;
	float currentR = radius - 0.001f * startDrawIndex;
	Matrix4f m;
	m = Matrix4f::createTranslation(position.x, position.y, position.z);
	if (!isRightPath) 
	{
		m = m*Matrix4f::createScale(-1, 1, 1) ;
	}

	Matrix4f rollMatirx;
	rollMatirx = Matrix4f::createTranslation(0.f, -radius, 0.f);
	rollMatirx = Matrix4f::createRotationAroundAxis(0, 0, rad2deg(angle)) * rollMatirx;
	rollMatirx = Matrix4f::createTranslation(hlength, currentR, 0.f) * rollMatirx;


	glUniformMatrix4fv(0, 1, GL_FALSE, m * rollMatirx);


	glBindVertexArray(vao);
	glDrawArrays(GL_TRIANGLE_STRIP, startDrawIndex * 2, vertexCount * 2 - startDrawIndex * 2);
	glBindVertexArray(vaoUnfold);
	float endU;
	if (isRightPath) {
		endU = this->startPos + percentage * (1 - this->startPos);
	} 
	else
	{
		endU = this->startPos - percentage * this->startPos;
	}
	this->UpdateUnFoldGL(vboUnfold, path, startDrawIndex, this->startPos, endU);
	glUniformMatrix4fv(0, 1, GL_FALSE, m);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

}

float UnfoldPicture::getPathLength()
{
	float length = 0.0f;
	if (pathRight != nullptr) {
		length += pathRight->getPathLength();
	}
	if (pathLeft != nullptr) {
		length += pathLeft->getPathLength();
	}

	return length;
}
